﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

namespace Lilium.LParse
{
    public static class TextParsers
    {
        static TextParsers()
        {
            AnyChar = state =>
                {
                    if (!state.Eof)
                    {
                        var current = state.Current;
                        state.Next();
                        return current;
                    }
                    else
                    {
                        state.Fail("any character");
                        return (char)0;
                    }
                };

            Letter = state =>
            {
                var result = state.Current;
                if (char.IsLetter(result) && !state.Eof)
                {
                    state.Next();
                    return result;
                }
                else
                {
                    state.Fail("letter");
                    return (char)0;
                }
            };
        }

        #region Parsers

        public static Parser<char, char> AnyChar;
        public static Parser<char, char> Letter;

        public static Parser<char, char> Char(char chr)
        {
            return state =>
            {
                if ((state.Current == chr) && !state.Eof)
                {
                    state.Next();
                    return chr;
                }
                else
                {
                    state.Fail(chr);
                    return (char)0;
                }
            };
        }

        public static Parser<char, char> Category(UnicodeCategory category)
        {
            return state =>
                {
                    var current = state.Current;
                    if ((char.GetUnicodeCategory(current)) == category && !state.Eof)
                    {
                        state.Next();
                        return current;
                    }
                    else
                    {
                        state.Fail();

                        if (state.CollectExceptedValues)
                            state.AppendExceptedValues(TextLabels.GetExceptedCategory(category));

                        return (char)0;
                    }
                };
        }

        public static Parser<char, TResult> Fail<TResult>(string exceptedValue)
        {
            return PrimParsers.Fail<char, TResult>(exceptedValue);
        }

        /// <summary>
        /// Scan input stream till the specified word.
        /// Semantically equivalent to TryWord(word).SkipTill().
        /// </summary>
        /// <param name="word"></param>
        /// <returns>
        /// </returns>
        public static Parser<char, string> Find(string word)
        { //TODO: переписать с использованием fлгоритма Кнута - Морриса - Пратта
            return TryWord(word).SkipTill();
        }

        /// <summary>
        /// Reads input stream till the <paramref name="end"/> parser satisfies.
        /// </summary>
        /// <param name="end"></param>
        /// <returns></returns>
        public static Parser<char, string> ManyTill<TEnd>(Parser<char, TEnd> end)
        {
            return state =>
            {
                end(state);

                if (!state.Failed)
                    return "";

                if (!state.Eof)
                {
                    var head = state.Current;
                    state.Next();

                    end(state);

                    if (state.Failed)
                    {
                        if (!state.Eof)
                        {
                            var result = new StringBuilder();
                            result.Append(head);
                            do
                            {
                                result.Append(state.Current);
                                state.Next();
                                end(state);
                                if (!state.Failed)
                                    return result.ToString();

                            } while (!state.Eof);
                            state.Fail("any character");
                        }
                    }
                    else
                        return head.ToString();
                }

                return "";
            };
        }

        public static Parser<char, string> Many1Till<TEnd>(Parser<char, TEnd> end)
        {
            return state =>
            {
                end(state);

                if (state.Failed && !state.Eof)
                {
                    var head = state.Current;
                    state.Next();

                    end(state);

                    if (state.Failed)
                    {
                        if (!state.Eof)
                        {
                            var result = new StringBuilder();
                            result.Append(head);
                            do
                            {
                                result.Append(state.Current);
                                state.Next();
                                end(state);
                                if (!state.Failed)
                                    return result.ToString();

                            } while (!state.Eof);
                        }
                    }
                    else
                        return head.ToString();
                }

                state.Fail("any character");
                return "";
            };
        }

        public static Parser<char, char> NoneOf(string chars)
        {
            return state =>
                {
                    var current = state.Current;
                    if ((chars.IndexOf(current) == -1) && !state.Eof)
                    {
                        state.Next();
                        return current;
                    }
                    else
                    {
                        state.Fail();

                        if (state.CollectExceptedValues)
                            state.AppendExceptedValues(TextLabels.GetNoneOfExceptedChars(chars));

                        return (char)0;
                    }
                };
        }

        public static Parser<char, char> Not(char chr)
        {
            return state =>
            {
                if ((state.Current != chr) && !state.Eof)
                {
                    var current = state.Current;
                    state.Next();
                    return current;
                }
                else
                {
                    state.Fail("not " + chr.ToString());
                    return (char)0;
                }
            };
        }

        public static Parser<char, char> OneOf(string chars)
        {
            return state =>
            {
                var current = state.Current;
                if ((chars.IndexOf(current) != -1) && !state.Eof)
                {
                    state.Next();
                    return current;
                }
                else
                {
                    state.Fail();

                    if (state.CollectExceptedValues)
                        state.AppendExceptedValues(TextLabels.GetExceptedChars(chars));

                    return (char)0;
                }
            };
        }

        public static Parser<char, char> Range(char lower, char upper)
        {
            return state =>
            {
                var current = state.Current;
                if ((current >= lower) && (current <= upper) && !state.Eof)
                {
                    state.Next();
                    return current;
                }
                else
                {
                    state.Fail();

                    if (state.CollectExceptedValues)
                        state.AppendExceptedValues(TextLabels.GetExceptedChars(lower, upper));

                    return (char)0;
                }
            };
        }

        /// <summary>
        /// Checks current token against the specified predicate, if succeed, moves forward, otherwise fails parser.
        /// </summary>
        /// <param name="predicate">Predicate</param>
        /// <returns>Parser</returns>
        /// <remarks>
        /// This specialized combinator simplifies type inference for text parsers.
        /// </remarks>
        public static Parser<char, char> Satisfy(Func<char, bool> predicate)
        {
            return state =>
            {
                var current = state.Current;
                if (!state.Eof && predicate(current))
                {
                    state.Next();
                    return current;
                }
                else
                {
                    state.Fail();
                    return (char)0;
                }
            };
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="exceptedValues">Values for error reporting</param>
        /// <returns></returns>
        /// <remarks>
        /// This specialized combinator simplifies type inference for text parsers.
        /// </remarks>
        public static Parser<char, char> Satisfy(Func<char, bool> predicate, IEnumerable<object> exceptedValues)
        {
            return state =>
            {
                var current = state.Current;
                if (!state.Eof && predicate(current))
                {
                    state.Next();
                    return current;
                }
                else
                {
                    state.Fail(exceptedValues);
                    return (char)0;
                }
            };
        }

        // strictly typed version of PrimParsers.SkipWhile to assist type inference.
        public static Parser<char, object> SkipWhile(Func<char, bool> predicate)
        {
            return PrimParsers.SkipWhile(predicate);
        }

        /// <summary>
        /// Tries to read one of the specified words.
        /// </summary>
        /// <param name="words"></param>
        /// <returns></returns>
        /// <remarks>
        /// This parser always uses Try combinator, hence always rollbacks on failure. 
        /// Reading words without Try combinator makes no much sense.
        /// </remarks>
        public static Parser<char, string> TryOneOf(IEnumerable<string> words)
        {
            return ParserCombinators.Choise(words.Select(word => TextParsers.TryWord(word)).ToArray());
        }

        public static Parser<char, string> TryWord(string word)
        {
            return Word(word).Try(word);
        }

        public static Parser<char, string> Word(string word)
        {
            return state =>
            {
                foreach (var chr in word)
                {
                    if ((state.Current == chr) && !state.Eof)
                        state.Next();
                    else
                    {
                        state.Fail(chr);
                        return null;
                    }
                }

                return word;
            };
        }

        #endregion

        #region Specialized combinators

        public static Parser<char, string> Many(this Parser<char, char> parser)
        {
            return state =>
            {
                var head = parser(state);
                if (!state.Failed)
                {
                    var body = parser(state);
                    if (!state.Failed)
                    {
                        var builder = new StringBuilder();
                        builder.Append(head);

                        do
                        {
                            builder.Append(body);
                            body = parser(state);
                        } while (!state.Failed);

                        state.Restore();
                        return builder.ToString();
                    }
                    else
                    {
                        state.Restore();
                        return head.ToString();
                    }
                }
                else
                {
                    state.Restore();
                    return "";
                }
            };
        }

        public static Parser<char, string> Many1(this Parser<char, char> parser)
        {
            return state =>
            {
                var head = parser(state);
                if (!state.Failed)
                {
                    var body = parser(state);
                    if (!state.Failed)
                    {
                        var builder = new StringBuilder();
                        builder.Append(head);

                        do
                        {
                            builder.Append(body);
                            body = parser(state);
                        } while (!state.Failed);

                        state.Restore();
                        return builder.ToString();
                    }
                    else
                    {
                        state.Restore();
                        return head.ToString();
                    }
                }
                else
                    return "";
            };
        }

        public static Parser<char, string> Many1(this Parser<char, char> parser, string label)
        {
            return state =>
            {
                state.SuspendValuesCollection();

                var head = parser(state);
                if (!state.Failed)
                {
                    var body = parser(state);
                    if (!state.Failed)
                    {
                        var builder = new StringBuilder();
                        builder.Append(head);

                        do
                        {
                            builder.Append(body);
                            body = parser(state);
                        } while (!state.Failed);

                        state.Restore();
                        state.ResumeValuesCollection(label);
                        return builder.ToString();
                    }
                    else
                    {
                        state.Restore();
                        state.ResumeValuesCollection(label);
                        return head.ToString();
                    }
                }
                else
                {
                    state.ResumeValuesCollection(label);
                    return "";
                }
            };
        }

        public static Parser<char, string> ManyTill<TEnd>(this Parser<char, char> parser, Parser<char, TEnd> end)
        {
            return state =>
            {
                end(state);

                if (!state.Failed)
                    return "";

                var head = parser(state);

                if (!state.Failed)
                {
                    end(state);

                    if (state.Failed)
                    {
                        var item = parser(state);

                        if (!state.Failed)
                        {
                            var result = new StringBuilder();
                            result.Append(head);

                            do
                            {
                                result.Append(item);
                                end(state);
                                if (!state.Failed)
                                    return result.ToString();

                                item = parser(state);
                            } while (!state.Failed);
                        }
                    }
                    else
                        return head.ToString();
                }

                return "";
            };
        }

        #endregion
    }
}
